#include "linux/fs.h"
#include "linux/slab.h"
#include "linux/spi.h"
#include "linux/of_platform.h"
#include "linux/init.h"
#include "linux/error.h"
#include "linux/blk.h"
#include "linux/ramfs.h"
#include "linux/common.h"
#include "linux/string.h"
#include "linux/printk.h"

#define M25P_WRITE_ENABLE 0x06
#define M25P_WRITE_DISABLE 0x04
#define M25P_READ_ID 0x9F
#define M25P_READ_STATUS_REG 0x05
#define M25P_WRITE_STATUS_REG 0x01
#define M25P_READ_DATA 0x03
#define M25P_FAST_READ 0x0B
#define M25P_PAGE_PROGRAM 0x02
#define M25P_SECTOR_ERASE 0xD8
#define M25P_BULK_ERASE 0xC7
#define M25P_DEEP_POWER_DOWN 0xB9
#define M25P_RELEASE_DPD 0xAB

// M25P128 参数
#define M25P_PAGE_SIZE 256
#define M25P_SECTOR_SIZE 4096
#define M25P_TOTAL_SIZE (16 * 1024 * 1024) // 16MB

// 状态寄存器位定义
#define M25P_STATUS_BUSY (1 << 0)
#define M25P_STATUS_WEL (1 << 1)


struct m25p_flash
{
	struct spi_device *spi;
	unsigned int sector_size;
};

static inline int m25pxx_write_bytes(struct m25p_flash *flash, uint8_t *data, uint32_t len)
{
	return spi_write(flash->spi, &data, len);
}

static inline int m25p_read_bytes(struct m25p_flash *flash, uint8_t *data, uint32_t len)
{
	return spi_read(flash->spi, data, len);
}

static inline int m25pxx_write_reg(struct m25p_flash *flash, uint8_t reg)
{
	return m25pxx_write_bytes(flash, &reg, 1);
}

static inline int m25pxx_read_reg(struct m25p_flash *flash, uint8_t reg, uint8_t *value)
{
	return spi_write_then_read(flash->spi, &reg, 1, value, 1);
}

static void m25pxx_wait_busy(struct m25p_flash *flash)
{
	uint8_t status;

	do 
	{
		if (m25pxx_read_reg(flash, M25P_READ_STATUS_REG, &status) < 0)
			break;
	} while (status & M25P_STATUS_BUSY);
}

static int m25p_release_deep_power_down(struct m25p_flash *flash)
{
	uint8_t buffer[4] = {M25P_RELEASE_DPD, 0, 0, 0};

	return spi_write(flash->spi, buffer, 4);
}

static int m25p_is_in_deep_power_down(struct m25p_flash *flash)
{
	int ret;
	uint8_t buffer[5] = {M25P_RELEASE_DPD, 0, 0, 0, 0};

	ret = spi_write_then_read(flash->spi, buffer, 4, buffer + 4, 1);
	if (ret < 0)
		return 0;
	
	return (buffer[4] == 0);
}

static int m25pxx_read_id(struct m25p_flash *flash, uint32_t *id)
{
	int ret;

	uint8_t buffer[4] = {M25P_READ_ID, 0, 0, 0};

	ret = spi_write_then_read(flash->spi, buffer, 1, buffer + 1, 3);
	if (ret < 0)
	{

	}

	*id = 0;
	*id = (((uint32_t)buffer[1]) << 16) |  (((uint32_t)buffer[2]) << 8) | (((uint32_t)buffer[3]) << 0);

	return ret;
}

static int m25pxx_write_enable(struct m25p_flash *flash)
{
	return m25pxx_write_reg(flash, M25P_WRITE_ENABLE);
}

static int m25pxx_write_disaable(struct m25p_flash *flash)
{
	return 0;
}

static int m25pxx_read_status(struct m25p_flash *flash, uint8_t *status)
{
	return 0;
}

static int m25pxx_write_status(struct m25p_flash *flash, uint8_t status)
{
	return 0;
}

static int m25pxx_sector_erase(struct m25p_flash *flash, uint32_t addr)
{
	int ret;
	uint8_t buffer[4] = {M25P_SECTOR_ERASE, (addr >> 16) & 0xff, (addr >> 8) & 0xff,  addr & 0xff};

	ret = m25pxx_write_enable(flash);
	if (ret < 0)
	{

	}

	ret = spi_write(flash->spi, buffer, 4);
	if (ret < 0)
	{

	}

	m25pxx_wait_busy(flash);

	return ret;
}

void m25pxx_gen_message(struct spi_message *m, struct spi_transfer *x, uint8_t *tx_buf, uint8_t *rx_buf, uint32_t len)
{
	x->len = len;
	x->rx_buf = rx_buf;
	x->tx_buf = tx_buf;
	spi_message_add_tail(x, m);
}

static int m25pxx_page_program(struct m25p_flash *flash, uint32_t addr, uint8_t *data, uint32_t len)
{
	int ret;
	uint8_t buffer[4] = {M25P_PAGE_PROGRAM, (addr >> 16) & 0xff, (addr >> 8) & 0xff, addr & 0xff};

	struct spi_message m;
	struct spi_transfer x[2];
	
	if (len > flash->sector_size)
		return -EINVAL;

    spi_message_init(&m);
    memset(x, 0, sizeof (x));

	m25pxx_gen_message(&m, &x[0], buffer, NULL, 4);
	m25pxx_gen_message(&m, &x[1], data, NULL, len);

    ret = spi_sync(flash->spi, &m);
    if (ret < 0)
	{

	}
	
	m25pxx_wait_busy(flash);

	return ret;
}

static int m25pxx_write_data(struct m25p_flash *flash, uint32_t addr, uint8_t *data, uint32_t len)
{
	int ret = 0;
	uint32_t remaining = len;
    uint32_t current_addr = addr;
    uint8_t *current_data = data;

	ret = m25pxx_write_enable(flash);
	if (ret < 0)
	{

	}

	while (remaining > 0)
	{
        uint32_t page_offset = current_addr % flash->sector_size;
        uint32_t write_size = flash->sector_size - page_offset;

        if (write_size > remaining)
            write_size = remaining;

		ret = m25pxx_page_program(flash, current_addr, current_data, write_size);
        if (ret < 0)
			break;

        current_addr += write_size;
        current_data += write_size;
        remaining -= write_size;
    }

	return ret;
}

static int m25pxx_read_data(struct m25p_flash *flash, uint32_t addr, uint8_t *data, uint32_t len)
{
	int ret;
	uint8_t buffer[] = {M25P_READ_DATA, (addr >> 16) & 0xff, (addr >> 8) & 0xff, addr & 0xff};

	struct spi_message m;
	struct spi_transfer x[2];
	
    spi_message_init(&m);
    memset(x, 0, sizeof (x));

	m25pxx_gen_message(&m, &x[0], buffer, NULL, sizeof (buffer));
	m25pxx_gen_message(&m, &x[1], NULL, data, len);

    ret = spi_sync(flash->spi, &m);
    if (ret < 0)
	{

	}
	
	m25pxx_wait_busy(flash);

	return ret;
}


int m25pxx_init(struct m25p_flash *flash)
{
	uint32_t id;
	int ret;

	ret = m25pxx_read_id(flash, &id);
	if (ret < 0)
	{

	}
	printk("find m25p device id: %x\n", id);

	if (m25p_is_in_deep_power_down(flash))
	{
		ret =  m25p_release_deep_power_down(flash);
		if (ret < 0)
		{

		}
	}


	return ret;
}

int m25pxx_do_request(struct request *req)
{
	int ret;
    unsigned long size = 0;
	struct m25p_flash *flash;

	flash = block_device_get_priv(req->dev);

    if (req->cmd == REQUEST_READ)
		ret = m25pxx_read_data(flash, req->sector << 8, (uint8_t *)(req->buffer), req->nr_sectors * flash->sector_size);
    else
		ret = m25pxx_write_data(flash, req->sector << 8, (uint8_t *)(req->buffer), req->nr_sectors * flash->sector_size);
	
	if (ret < 0)
		return ret;
	
	size = req->nr_sectors * flash->sector_size;
    return size;
}

int m25pxx_get_disk_info(struct disk_info *info)
{
	if (!info)
		return -EINVAL;
	
	info->sector_size = 256;

	return 0;
}

int m25pxx_open(struct inode *inode, struct file *filp)
{
	return 0;
}

void m25pxx_release(struct inode *inode, struct file *filp)
{

}

struct file_operations m25pxx_file_operations = 
{
	.open = m25pxx_open,
	.release = m25pxx_release,
};

int m25pxx_probe(struct spi_device *pdev)
{
	int ret = 0;
    int major = 10;
	struct m25p_flash *flash;

	flash = kmalloc(sizeof (struct m25p_flash), GFP_KERNEL);
	if (!flash)
		return -ENOMEM;
	
	flash->spi = pdev;
	flash->sector_size = M25P_PAGE_SIZE;

	ret = m25pxx_init(flash);
	if (ret < 0)
	{

	}

    ret = register_blkdev(major, "m25pxx", &m25pxx_file_operations);
	if (ret)
	{
		//need release something?
		return ret;
	}

	block_device_set_priv(major, flash);

    ret = register_get_disk_info_fun(major, m25pxx_get_disk_info);
	if (ret)
	{
		//need release something?
		return ret;
	}


	//if error, need release something?
   	ret = register_blk_request(major, m25pxx_do_request);
	if (ret < 0)
	{

	}
	ret = sys_mknod("/dev/m25pxx", S_IFBLK, major);
	if (ret < 0)
	{

	}

	return ret;
}

static struct of_device_id m25pxx_dt_ids[] = {
	{ .compatible = "st,m25p80", .data =  NULL },
	{ .compatible = "st,m25p128", .data =  NULL },
	{ /* sentinel */ }
};

static struct spi_driver m25pxx_driver = {
    .driver = {
        .name = "m25pxx",
        .of_match_table = m25pxx_dt_ids,
    },
    
    .probe = m25pxx_probe,
};

static int __init m25pxx_module_init(void)
{
    return spi_register_driver(&m25pxx_driver);
}

static void m25pxx_module_exit(void)
{
}

module_init(m25pxx_module_init);
module_exit(m25pxx_module_exit);